﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;

using xEngine.DataTypes;

namespace xEngine.Actors.Terrain
{
    public class TerrainVertexCollection : VertexCollection<VertexPositionNormalTexture>
    {
        #region Member Variables

        private int _size;
        private int _halfSize;

        #endregion

        #region Properties

        public Vector3 HighestPoint { get; private set; }

        #endregion

        #region Constructors

        public TerrainVertexCollection(Texture2D heightMap) : base(heightMap.Width * heightMap.Width)
        {
            _halfSize = (_size = heightMap.Width - 1) / 2;
            HighestPoint = Vector3.Zero;
            BuildVertexData(ExtractHeightData(heightMap));
            CalculateNormals(Vector3.One);
        }

        #endregion

        #region Functions

        private float[] ExtractHeightData(Texture2D heightMap)
        {
            float[] output = new float[VertexCount];
            
            switch (heightMap.Format)
            {
                case SurfaceFormat.Color:       // Png8
                    {
                        Color[] heightMapColors = new Color[VertexCount];
                        heightMap.GetData<Color>(heightMapColors);

                        for (int i = 0; i < heightMapColors.Length; i++)
                            output[i] = heightMapColors[i].R / 255.0f;
                    }
                    break;

                case SurfaceFormat.Alpha8:      // Raw8
                    {
                        Alpha8[] heightMapColors = new Alpha8[VertexCount];
                        heightMap.GetData<Alpha8>(heightMapColors);

                        for (int i = 0; i < heightMapColors.Length; i++)
                            output[i] = heightMapColors[i].PackedValue / 255.0f;
                    }
                    break;

                case SurfaceFormat.HalfSingle:  // Raw16
                    {
                        HalfSingle[] heightMapColors = new HalfSingle[VertexCount];
                        heightMap.GetData<HalfSingle>(heightMapColors);

                        for (int i = 0; i < heightMapColors.Length; i++)
                            output[i] = heightMapColors[i].PackedValue / 65535.0f;
                    }
                    break;
            }

            return output;
        }
        private void BuildVertexData(float[] heightData)
        {
            float x = 0, z = 0;

            for (int i = 0; i < VertexCount; i++)
            {
                if (x > _size)
                {
                    x = 0;
                    z++;
                }

                VertexPositionNormalTexture vertex;

                vertex.Position = new Vector3(x - _halfSize, heightData[i], z - _halfSize);
                vertex.Normal = Vector3.Zero;
                vertex.TextureCoordinate = new Vector2(x, z);

                if (vertex.Position.Y > HighestPoint.Y)
                    HighestPoint = vertex.Position;

                this[i] = vertex;

                x++;
            }
        }
        public void ResetNormals()
        {
            for (int i = 0; i < VertexCount; i++)
            {
                VertexPositionNormalTexture vertex = this[i];
                vertex.Normal = Vector3.Zero;
                this[i] = vertex;
            }
        }
        public void CalculateNormals(Vector3 scale)
        {
            if (VertexCount < 9)
                return;

            // Triangle indexers
            int i = _size + 2,
                j = 0,
                k = i + _size;

            /*
                j     j+1    j+2
                *      *      *
     
               i-1     i     i+2         
                *      *      *
   
                k     k+1    k+2    
                *      *      *
            */

            for (int n = 0; i <= (VertexCount - _size) - 2; i += 2, n++, j += 2, k += 2)
            {
                // If end of stipe wrap to the next one
                if (n == _size / 2)
                {
                    n = 0;
                    i += _size + 2;
                    j += _size + 2;
                    k += _size + 2;
                }

                //Calculate normals for each of the 8 triangles
                SetNormals(i, j, j + 1, scale);
                SetNormals(i, j + 1, j + 2, scale);
                SetNormals(i, j + 2, i + 1, scale);
                SetNormals(i, i + 1, k + 2, scale);
                SetNormals(i, k + 2, k + 1, scale);
                SetNormals(i, k + 1, k, scale);
                SetNormals(i, k, i - 1, scale);
                SetNormals(i, i - 1, j, scale);
            }
        }
        private void SetNormals(int idx1, int idx2, int idx3, Vector3 scale)
        {
            if (idx3 > VertexCount - 1)
                idx3 = VertexCount - 1;

            VertexPositionNormalTexture v1 = this[idx1], v2 = this[idx2], v3 = this[idx3];

            Vector3 p1 = v1.Position * scale;
            Vector3 p2 = v2.Position * scale;
            Vector3 p3 = v3.Position * scale;

            Vector3 normal = Vector3.Normalize(Vector3.Cross(p2 - p1, p1 - p3));

            // Addition instead of assignment in order to smooth the normals
            v1.Normal += normal;
            v2.Normal += normal;
            v3.Normal += normal;

            this[idx1] = v1;
            this[idx2] = v2;
            this[idx3] = v3;

            /*
                     \   <= Using assignments
                    \/\/
                   \/  \/
                  \/    \/
                 \/      \/
             
                      |   <= Using additions
                    \/ \/
                   \/   \/
                  \/     \/
                 \/       \/
            */
        }

        #endregion
    }
}
